package com.google.code.mergeinformer.controller;

import com.google.code.mergeinformer.model.Project;
import com.google.code.mergeinformer.model.Task;
import com.google.code.mergeinformer.service.ProjectService;
import com.google.code.mergeinformer.service.TaskService;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.*;

/**
 * @author Dmitry Golubev
 */
public abstract class ControllerBase {

    private static final String REDIRECT_BASE = "redirect:";

    private static final String SA_CURRENT = "sa_current";

    public static final String SA_LAST_URL = "sa_lastUrl";
    public static final String SA_NOTIFICATIONS = "sa_notifications";

    private static final String RA_CURRENT_PROJECT = "currentProject";
    private static final String RA_CURRENT_TASK = "currentTask";

    @Autowired
    protected HttpServletRequest request;
    @Autowired
    protected HttpSession session;

    @Autowired
    protected Validator validator;

    @Autowired
    protected ProjectService projectService;
    @Autowired
    protected TaskService taskService;

    protected void setCurrent(Project project, Task task) {
        request.setAttribute(RA_CURRENT_PROJECT, project);
        request.setAttribute(RA_CURRENT_TASK, task);
        final String projectShortName = project != null ? project.getShortName() : null;
        final String taskShortName = project != null && task != null ? task.getShortName() : null;
        session.setAttribute(SA_CURRENT, new CurrentWrapper(projectShortName, taskShortName));
    }

    protected void preserveCurrent() {
        Project currentProject = null;
        Task currentTask = null;
        final CurrentWrapper wrapper = (CurrentWrapper) session.getAttribute(SA_CURRENT);
        if (wrapper != null) {
            final String projectShortName = wrapper.getProjectShortName();
            if (projectShortName != null) {
                currentProject = projectService.getByShortName(projectShortName);
                if (currentProject != null) {
                    final String taskShortName = wrapper.getTaskShortName();
                    if (taskShortName != null) {
                        currentTask = taskService.getByShortName(projectShortName, taskShortName);
                    }
                }
            }
        }
        request.setAttribute(RA_CURRENT_PROJECT, currentProject);
        request.setAttribute(RA_CURRENT_TASK, currentTask);
    }

    protected CurrentWrapper getCurrentFromSession() {
        return  (CurrentWrapper) session.getAttribute(SA_CURRENT);
    }

    protected String getRedirect() {
        return REDIRECT_BASE + "/";
    }

    protected String getRedirect(Project project) {
        return getRedirect(project.getShortName());
    }

    protected String getRedirect(String projectShortName) {
        return REDIRECT_BASE + "/" + projectShortName;
    }

    protected String getRedirect(Project project, Task task) {
        return getRedirect(project.getShortName(), task.getShortName());
    }

    protected String getRedirect(String projectShortName, String taskShortName) {
        return REDIRECT_BASE + "/" + projectShortName + "/" + taskShortName;
    }

    protected String getFallback() {
        return REDIRECT_BASE + session.getAttribute(SA_LAST_URL);
    }

    protected ValidationResult validate(Object target, Class<?>... group) {
        final Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target, group);
        final List<Violation> violations = new LinkedList<>();
        for (ConstraintViolation violation : constraintViolations) {
            violations.add(new Violation(violation.getPropertyPath().toString(), violation.getInvalidValue(), violation.getMessage()));
        }
        return new ValidationResult(violations);
    }

    @SuppressWarnings("unchecked")
    protected void pushNotifications(Notification... notification) {
        synchronized (WebUtils.getSessionMutex(session)) {
            List<Notification> notifications = (List<Notification>) session.getAttribute(SA_NOTIFICATIONS);
            if (notifications == null) {
                notifications = new LinkedList<>();
                session.setAttribute(SA_NOTIFICATIONS, notifications);
            }
            Collections.addAll(notifications, notification);
        }
    }

    protected Notification buildNotification(Notification.Type type, String title, String text, Long elapsedTimeMillis) {
        if (elapsedTimeMillis != null) {
            text += "<br/>Elapsed time: <b>" + elapsedTimeMillis + "</b> ms";
        }
        return new Notification(type, title, text);
    }

    protected boolean isCurrentProject(String projectShortName) {
        final CurrentWrapper wrapper = (CurrentWrapper) session.getAttribute(SA_CURRENT);
        if (wrapper == null) {
            return projectShortName == null;
        }
        return Objects.equals(wrapper.getProjectShortName(), projectShortName);
    }

    protected boolean isCurrentTask(String projectShortName, String taskShortName) {
        final CurrentWrapper wrapper = (CurrentWrapper) session.getAttribute(SA_CURRENT);
        if (wrapper == null) {
            return projectShortName == null && taskShortName == null;
        }
        return Objects.equals(wrapper.getProjectShortName(), projectShortName)
                && Objects.equals(wrapper.getTaskShortName(), taskShortName);
    }

    public static class Notification {
        private final Type type;
        private final String title;
        private final String text;

        public Notification(Type type, String title, String text) {
            this.type = type;
            this.title = StringEscapeUtils.escapeJavaScript(title);
            this.text = StringEscapeUtils.escapeJavaScript(text);
        }

        public Type getType() {
            return type;
        }

        public String getTitle() {
            return title;
        }

        public String getText() {
            return text;
        }

        public static enum Type {
            success, info, notice, error
        }
    }

    public static class ValidationResult {
        private boolean valid;
        private final List<Violation> violations;
        private Object data;

        public ValidationResult(List<Violation> violations) {
            this.valid = violations.size() == 0;
            this.violations = violations;
        }

        public boolean isValid() {
            return valid;
        }

        public List<Violation> getViolations() {
            return violations;
        }

        public void addViolation(Violation violation) {
            if (violation == null) {
                return;
            }
            violations.add(violation);
            valid = false;
        }

        public void addViolation(String property, Object value, String message) {
            addViolation(new Violation(property, value, message));
        }

        public Object getData() {
            return data;
        }

        public ValidationResult setData(Object data) {
            this.data = data;
            return this;
        }
    }

    public static final class Violation {
        private String property;
        private Object value;
        private String message;

        public Violation(String property, Object value, String message) {
            this.property = property;
            this.value = value;
            this.message = message;
        }

        public String getProperty() {
            return property;
        }

        public Object getValue() {
            return value;
        }

        public String getMessage() {
            return message;
        }
    }

    protected static final class CurrentWrapper {
        private final String projectShortName;
        private final String taskShortName;

        public CurrentWrapper(String projectShortName, String taskShortName) {
            this.projectShortName = projectShortName;
            this.taskShortName = taskShortName;
        }

        public String getProjectShortName() {
            return projectShortName;
        }

        public String getTaskShortName() {
            return taskShortName;
        }
    }
}
